package persistencia.carreras;

import java.io.PrintStream;
import java.util.Observable;

import modelo.carrera.Carrera;
import modelo.carrera.Jugador;
import modelo.carrera.ObservadorDeCarrera;
import modelo.controles.Control;
import modelo.escenario.Ciudad;
import modelo.escenario.Cuadra;
import modelo.geometria.Vector;
import modelo.obstaculos.Obstaculo;
import modelo.seniales.ISenial;
import modelo.seniales.Semaforo;
import modelo.seniales.carteles.CirculacionExclusiva;
import modelo.seniales.carteles.LimiteVelocidad;
import modelo.vehiculos.Ambulancia;
import modelo.vehiculos.SentidoVehiculo;
import modelo.vehiculos.Vehiculo;

public class ObservadorDeCarreraPrintStream extends ObservadorDeCarrera {
	protected boolean iniciada;
	protected PrintStream salida;

	public ObservadorDeCarreraPrintStream(PrintStream salida) {
		this.salida = salida;
		iniciada = false;
	}
	
	protected void armarSimple(String tag, String valor) {
		salida.println( escribirSimple(tag, valor));
	}

	protected String escribirSimple(String tag, String valor) {
		return "<"+tag+">"+valor+"</"+tag+">";
	}

	protected double porcientoDe(double inicio, double actual, double fin) {
		double total, recorrido, porciento = 0;
		
		if ((actual<inicio) && (actual<fin))
			return 0;
		if ((actual>inicio) && (actual>fin))
			return 100;
	
		if ((actual>=inicio) && (actual<=fin)) {
			total = fin - inicio;
			recorrido = actual - inicio;
			
			porciento = (recorrido/total)*100;
		} else
			porciento = porcientoDe(fin,actual,inicio);
		return porciento;
	}

	protected long calcularPosicion(Vector inicio, Vector actual, Vector fin) {
		double porcientoX = porcientoDe(inicio.getX(), actual.getX(), fin.getX());
		double porcientoY = porcientoDe(inicio.getY(), actual.getY(), fin.getY());

		return (long)Math.hypot(porcientoX, porcientoY);
	}
	
	protected String getTipo(Obstaculo obstaculo) {
		if (obstaculo instanceof modelo.obstaculos.Bache)
			return "POZO";
		if (obstaculo instanceof modelo.obstaculos.Parada)
			return "PARADA";
		if (obstaculo instanceof modelo.obstaculos.Vereda)
			return "VEREDA";		
		return null;
	}
	
	protected void armarObstaculo(Obstaculo obstaculo, Cuadra cuadra, Ciudad ciudad) {
		// Si el obstaculo esta en la cuadra.
		Cuadra masCercana = cuadraMasCercana(obstaculo, ciudad);
		if ((masCercana != null) && (cuadra.getId().compareTo(masCercana.getId()) == 0)) {
			Vector inicio = cuadra.getZonaComienzo().getCentro();
			Vector fin = cuadra.getZonaFin().getCentro();
			Long posicion = calcularPosicion(inicio,obstaculo.getPosicion(),fin);
			String tipo = getTipo(obstaculo);
			if (tipo != null) {
				salida.println("<obstaculo posicion=\""+posicion+"\">");
				armarSimple("tipo", tipo);
				salida.println("</obstaculo>");
			}
		}
	}
	
	protected Cuadra cuadraMasCercana(Obstaculo obstaculo, Ciudad ciudad) {
		Cuadra masCercana = null;
		double minimaDistancia = Double.POSITIVE_INFINITY;
		
		for (Cuadra cuadra : ciudad.getGuiaT().obtenerCuadras()) {
			double distancia = obstaculo.getPosicion().getDistancia(cuadra.getSuperficie().getCentro());
			if (distancia < minimaDistancia) {
				minimaDistancia = distancia;
				masCercana = cuadra;
			}
		}
		return masCercana;
	}

	protected void armarAtributosDeSenial(ISenial senial) {
		if (senial instanceof LimiteVelocidad) {
			LimiteVelocidad limite = (LimiteVelocidad)senial;
			Double velocidad = limite.getVelocidad();
			Boolean deMaxima = limite.esLimiteDeVelocidadMaxima();
			armarSimple("limite", velocidad.toString());
			armarSimple("deMaxima", deMaxima.toString());
		} else if (senial instanceof CirculacionExclusiva) {
			CirculacionExclusiva exclusiva = (CirculacionExclusiva)senial;
			String tipoVehiculo = getTipo(exclusiva.getVehiculoPermitido());
			armarSimple("vehiculoPermitido", tipoVehiculo);
		}
	}
	
	protected String getTipo(ISenial senial) {
		return senial.getClass().getSimpleName();
	}
	
	protected void armarSenial(Obstaculo obstaculo, Cuadra cuadra, Ciudad ciudad) {
		if (obstaculo instanceof ISenial) {
			ISenial comoSenial = (ISenial)obstaculo;
			Cuadra masCercana = cuadraMasCercana(obstaculo, ciudad);

			if (cuadra.getId().compareTo(masCercana.getId()) == 0) {
				Vector inicio = cuadra.getZonaComienzo().getCentro();
				Vector fin = cuadra.getZonaFin().getCentro();
				Long posicion = calcularPosicion(inicio,obstaculo.getPosicion(),fin);
				String tipo = getTipo(comoSenial);
				String id = obstaculo.getIdentificador();
				String orientacion = SentidoVehiculo.ESTE.toString();
				String borde = orientacion;
				
				if (tipo != null) {
					salida.println("<senial id=\""+id+"\" posicion=\""+posicion+"\">");
					armarSimple("tipo", tipo);
					armarAtributosDeSenial(comoSenial);
					armarSimple("orientacion", orientacion);
					armarSimple("borde", borde);
					salida.println("</senial>");
				}
			}
		}
	}
	
	protected void armarCalle(Cuadra cuadra, Ciudad ciudad) {
		String id = cuadra.getId();
		Long longitud = 100L;
		salida.println("<calle id=\""+id+"\" longitud=\""+longitud+"\">");

		String conexion = cuadra.getConexionCruce(cuadra.getCruceDestino()).toString();
		armarSimple("conexion", conexion);
		
		salida.println("<carriles>");
		salida.println("<carril numero=\"1\">");
			String superficie = "PAVIMENTO";
			armarSimple("superficie",superficie);
			String sentido = cuadra.getSentido().toString();
			armarSimple("sentido", sentido);

				salida.println("<obstaculos>");
				for (Obstaculo obstaculo : ciudad.getObstaculos())
					armarObstaculo(obstaculo, cuadra, ciudad);
				salida.println("</obstaculos>");
		salida.println("</carril>");
		salida.println("</carriles>");
		
		salida.println("<seniales>");
		for (Obstaculo obstaculo : ciudad.getObstaculos())	
			armarSenial(obstaculo, cuadra, ciudad);
		salida.println("</seniales>");

		Control sancionador = null;
		for (Control control : ciudad.getControles())  {
			if (cuadra.getSuperficie().solapa(control.getPosicion())) {
				sancionador = control;
				break;
			}
		}
			
		if (sancionador != null) {
			Vector inicio = cuadra.getZonaComienzo().getCentro();
			Vector fin = cuadra.getZonaFin().getCentro();
			Long posicion = calcularPosicion(inicio,sancionador.getPosicion(),fin);
			String tipo = getTipo(sancionador.getSenial());
			String orientacion = cuadra.getConexionCruce(cuadra.getCruceOrigen()).toString();
			String borde = orientacion;
			
			if (tipo != null) {
				salida.println("<sancionador posicion=\""+posicion+"\">");
				armarSimple("tipo", tipo);
				armarSimple("orientacion", orientacion);
				armarSimple("borde", borde);
				salida.println("</sancionador>");
			}
		}
		salida.println("</calle>");
	}
	
	protected String getTipo(Vehiculo vehiculo) {
		return vehiculo.getClass().getSimpleName();
	}
	
	protected Jugador obtenerJugadorDe(Vehiculo vehiculo, Carrera carrera) {
		Jugador buscado = null;
		for (Jugador jugador : carrera.getJugadores()) {
			if (jugador.getVehiculo() == vehiculo) {
				buscado = jugador;
				break;
			}
		}
		return buscado;
	}
	protected void armarVehiculo(Vehiculo vehiculo, Carrera carrera) {
		String id = vehiculo.getIdentificador();
		String tipo = getTipo(vehiculo);
		
		String conductor = "Simulador";
		Jugador jugador = obtenerJugadorDe(vehiculo, carrera);
		if (jugador != null)
			conductor = jugador.getNombre();

		if (tipo != null) {
			salida.println("<vehiculo id=\""+id+"\">");
			armarSimple("conductor", conductor);
			armarSimple("tipoVehiculo", tipo);
			salida.println("</vehiculo>");
		}
	}
	
	protected void armarMapa(Carrera carrera) {
		Ciudad ciudad = carrera.getEscenario();
		//TODO obtener el ancho y largo de la ciudad << atributo Cuerpo.
		long alto = 1000;
		long ancho = 618;
		salida.println("<mapa ancho=\""+ ancho +"\" alto=\""+ alto +"\">");
		
		salida.println("<calles>");
		for (Cuadra cuadra : ciudad.getGuiaT().obtenerCuadras())
			armarCalle(cuadra, ciudad);
		salida.println("</calles>");
		
		salida.println("<vehiculos>");
		for (Vehiculo vehiculo : ciudad.getVehiculos())
			armarVehiculo(vehiculo, carrera);
		salida.println("</vehiculos>");
		
		salida.println("</mapa>");
		salida.println("<ticks>");
	}
	
	@Override
	protected void enInicioDeJuego() {
		salida.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
		salida.println("<juego>");
		iniciada = true;
	}
	
	@Override
	protected void enFinDeJuego() {
		if (iniciada)
			salida.println("</ticks>");
		salida.println("</juego>");
	}
	
	protected void armarTick(Carrera carrera, long instante) {
		Ciudad ciudad = carrera.getEscenario();
		salida.println("<tick numero=\""+instante+"\">");
		
		salida.println("<senialesTransito>");
		for (Semaforo semaforo : ciudad.getSemaforos())
			armarCambioDeSemaforo(semaforo);
		salida.println("</senialesTransito>");
		
		salida.println("<vehiculos>");
		for (Vehiculo vehiculo : ciudad.getVehiculos())
			armarCambioDeVehiculo(vehiculo, carrera);
		
		salida.println("</vehiculos>");
		
		salida.println("</tick>");
	}
	
	protected void armarCambioDeVehiculo(Vehiculo vehiculo, Carrera carrera) {
		String id = vehiculo.getIdentificador();
		Cuadra cuadra = carrera.getEscenario().getCuadra(vehiculo.getPosicion());
		Long carril = 1L;
		Long posicion = null;
		Integer puntos = null;
		Jugador jugador = null;
		if (cuadra != null) {
			Vector inicio = cuadra.getZonaComienzo().getCentro();
			Vector fin = cuadra.getZonaFin().getCentro();
			posicion = calcularPosicion(inicio,vehiculo.getPosicion(),fin);
			jugador = obtenerJugadorDe(vehiculo, carrera);
			if (jugador != null)
				puntos = jugador.getPuntos();
		}
		
		salida.println("<vehiculo id=\""+id+"\">");
		if (cuadra != null)
			armarSimple("idcalle", cuadra.getId());
		armarSimple("numCarril", carril.toString());
		if (posicion != null)
			armarSimple("posicion", posicion.toString());
		if (puntos != null)
			armarSimple("puntos",puntos.toString());
		
		if (vehiculo instanceof Ambulancia) {
			Ambulancia ambulancia = (Ambulancia)vehiculo;
			String sirena = "OFF";
			if (ambulancia.isSirenaActiva())
				sirena = "ON";
			armarSimple("sirena",sirena);
		}
		salida.println("</vehiculo>");
	}

	protected void armarCambioDeSemaforo(Semaforo semaforo) {
		String id = semaforo.getIdentificador();
		String color = semaforo.getColor().toString();
		
		salida.println("<senial id=\""+id+"\">");
		armarSimple("color", color);
		salida.println("</senial>");
	}	
}
